﻿using System;
using System.Collections.Generic;
using System.IO;
namespace ImryStefanVRPprogram
{
    /// <summary>
    /// Describes a solution to a CVRP 
    /// </summary>
    public class CVRPSolution
    {
        private double _totalCost;
        public List<VehicleRoute> listOfRoutes { get; set; }
        /// <summary>
        /// Descibes the actual tours in the solution
        /// </summary>

        public double totalCost
        {
            get
            {
                return _totalCost;
            }
            set
            {
                _totalCost = value;
                if (Program.DEBUG)
                {
                    double realTrialSolutionCost = 0;
                    foreach (VehicleRoute r in listOfRoutes)
                    {
                        for (int i = 0; i < r.GetNumberOfNodesVisited() + 1; i++)
                            realTrialSolutionCost += Program.vrpData.Distances[r.GetNodeAt(i), r.GetNodeAt(i + 1)];
                    }
                    if (Math.Abs(realTrialSolutionCost - _totalCost) > 1e-6)
                        throw new Exception("Cost computed incorrectly.");
                }
            }
        }

        public CVRPSolution()//empty constructor
        {
 
        }

        public CVRPSolution(CVRPSolution Solution) // new constructor
        {
            
            this.listOfRoutes = new List<VehicleRoute>();
            foreach (VehicleRoute route in Solution.listOfRoutes)
            {

                List<int> newNodesArray = new List<int>();
                for (int i = 0; i <= route.GetNumberOfNodesVisited() + 1; i++)
                    newNodesArray.Add(route.GetNodeAt(i));
                VehicleRoute newRoute = new VehicleRoute(newNodesArray);
                newRoute.totalDemandOnRoute = route.totalDemandOnRoute;
                newRoute.totalDistanceOfRoute = route.totalDistanceOfRoute;
                this.listOfRoutes.Add(newRoute);
            }
            this.totalCost = Solution.totalCost;
        }

        internal void WriteToFile(string p, TimeSpan ts, double lambda, double mu, double nu)
        {
            StreamWriter file = new StreamWriter(p);
            int ii = 0;
            foreach (VehicleRoute tour in listOfRoutes)
            {
                file.Write("Route #" + ii + ":");
                for (int j = 1; j <= listOfRoutes[ii].GetNumberOfNodesVisited(); j++)
                {
                    file.Write(" " + listOfRoutes[ii].GetNodeAt(j));
                }
                file.WriteLine();
                ii++;
            }
            file.WriteLine("Cost " + totalCost);
            file.WriteLine("Time " + ts.TotalMilliseconds);
            file.WriteLine("Lambda" + lambda);
            file.WriteLine("Mu" + mu);
            file.WriteLine("Nu" + nu); 
            file.Close();
        }

        /// <summary>
        /// Copies the full solution, including the routes to a new object 
        /// </summary>
        internal CVRPSolution Clone()
        {
            CVRPSolution cloneOfTheCurrentSolution = new CVRPSolution();
            cloneOfTheCurrentSolution.listOfRoutes = new List<VehicleRoute>();
            foreach (VehicleRoute route in listOfRoutes)
            {

                List<int> newNodesArray = new List<int>();
                for(int i=0;i<=route.GetNumberOfNodesVisited()+1;i++)
                    newNodesArray.Add(route.GetNodeAt(i));
                VehicleRoute newRoute = new VehicleRoute(newNodesArray);
                newRoute.totalDemandOnRoute = route.totalDemandOnRoute;
                cloneOfTheCurrentSolution.listOfRoutes.Add(newRoute);
            }
            cloneOfTheCurrentSolution.totalCost = totalCost;
            return cloneOfTheCurrentSolution;
        }

       

        internal void DisplayRoutes()
        {
            foreach (VehicleRoute v in listOfRoutes)
            {
                for (int j = 0; j < v.GetNumberOfNodesVisited(); j++)
                {
                    Console.Write(v.GetNodeAt(j + 1) + " ");
                }
                Console.WriteLine();
            }
        }

        internal void FeasibilityCheck()
        {
            int[] nodes = new int[Program.vrpData.Dimension];
            nodes[0] = 1;

            for (int i = 0; i < listOfRoutes.Count; i++)
            {
                double demand = 0;
                for (int j = 1; j <= listOfRoutes[i].GetNumberOfNodesVisited(); j++)
                {
                    demand = Program.vrpData.Demands[listOfRoutes[i].GetNodeAt(j)];
                    nodes[listOfRoutes[i].GetNodeAt(j)]++;
                }
                if (demand > Program.vrpData.Capacity)
                    throw new Exception("A route in the solution exceeds capacity!");
            }

            for (int i = 0; i < Program.vrpData.Dimension; i++)
            {
                if (nodes[i] > 1)
                    throw new Exception("Using node too many times!");
                if (nodes[i] < 1)
                    throw new Exception("Not visiting a node!");
            }
            Console.WriteLine("Feasibility check passed.");
        }

        internal void UndoChange(ChangeInSolution theChangeToUndo)
        {
            switch (theChangeToUndo.thisMoveType)
            {
                case MoveType.InjectionWhichPerservesBothRoutes:                    
                    listOfRoutes[theChangeToUndo.routeA].InsertNodeAt(theChangeToUndo.spotA, listOfRoutes[theChangeToUndo.routeB].GetNodeAt(theChangeToUndo.spotB));
                    listOfRoutes[theChangeToUndo.routeB].RemoveNodeAt(theChangeToUndo.spotB);
                    break;
                case MoveType.InjectionWhichRemovesARoute:
                    listOfRoutes.Insert(theChangeToUndo.routeA, new VehicleRoute(listOfRoutes[theChangeToUndo.routeB].GetNodeAt(theChangeToUndo.spotB)));
                    listOfRoutes[theChangeToUndo.routeB].RemoveNodeAt(theChangeToUndo.spotB);
                    break;
                case MoveType.Swap:
                    int nodeAtSpotA = listOfRoutes[theChangeToUndo.routeA].GetNodeAt(theChangeToUndo.spotA);
                    listOfRoutes[theChangeToUndo.routeA].SwitchNode(theChangeToUndo.spotA, listOfRoutes[theChangeToUndo.routeB].GetNodeAt(theChangeToUndo.spotB));
                    listOfRoutes[theChangeToUndo.routeB].SwitchNode(theChangeToUndo.spotB, nodeAtSpotA);
                    break;
            }
            listOfRoutes[theChangeToUndo.routeA].totalDemandOnRoute = theChangeToUndo.prevDemandOnA;
            listOfRoutes[theChangeToUndo.routeB].totalDemandOnRoute = theChangeToUndo.prevDemandOnB;
            listOfRoutes[theChangeToUndo.routeA].totalDistanceOfRoute = theChangeToUndo.prevDistOnA;
            listOfRoutes[theChangeToUndo.routeB].totalDistanceOfRoute = theChangeToUndo.prevDistOnB;
            totalCost=theChangeToUndo.totalCostBeforeChange;
        }

        internal void ApplyChange(ChangeInSolution theChangeToBeMade)
        {
            switch (theChangeToBeMade.thisMoveType)
            {
                case MoveType.InjectionWhichPerservesBothRoutes:
                case MoveType.InjectionWhichRemovesARoute:
                    listOfRoutes[theChangeToBeMade.routeB].InsertNodeAt(theChangeToBeMade.spotB, listOfRoutes[theChangeToBeMade.routeA].GetNodeAt(theChangeToBeMade.spotA));
                    listOfRoutes[theChangeToBeMade.routeA].RemoveNodeAt(theChangeToBeMade.spotA);
                    listOfRoutes[theChangeToBeMade.routeA].totalDemandOnRoute = theChangeToBeMade.nextDemandOnA;
                    listOfRoutes[theChangeToBeMade.routeB].totalDemandOnRoute = theChangeToBeMade.nextDemandOnB;
                    listOfRoutes[theChangeToBeMade.routeA].totalDistanceOfRoute = theChangeToBeMade.nextDistOnA;
                    listOfRoutes[theChangeToBeMade.routeB].totalDistanceOfRoute = theChangeToBeMade.nextDistOnB;
                    if (listOfRoutes[theChangeToBeMade.routeA].GetNumberOfNodesVisited() == 0)
                        listOfRoutes.RemoveAt(theChangeToBeMade.routeA);
                    break;
                case MoveType.Swap:
                    int nodeAtSpotA = listOfRoutes[theChangeToBeMade.routeA].GetNodeAt(theChangeToBeMade.spotA);
                    listOfRoutes[theChangeToBeMade.routeA].SwitchNode(theChangeToBeMade.spotA, listOfRoutes[theChangeToBeMade.routeB].GetNodeAt(theChangeToBeMade.spotB));
                    listOfRoutes[theChangeToBeMade.routeB].SwitchNode(theChangeToBeMade.spotB, nodeAtSpotA);
                    listOfRoutes[theChangeToBeMade.routeA].totalDemandOnRoute = theChangeToBeMade.nextDemandOnA;
                    listOfRoutes[theChangeToBeMade.routeB].totalDemandOnRoute = theChangeToBeMade.nextDemandOnB;
                    listOfRoutes[theChangeToBeMade.routeA].totalDistanceOfRoute = theChangeToBeMade.nextDistOnA;
                    listOfRoutes[theChangeToBeMade.routeB].totalDistanceOfRoute = theChangeToBeMade.nextDistOnB;
                    break;
            }
            totalCost=theChangeToBeMade.totalCostAfterChange;
        }
      
    }
}
